﻿// <copyright file="AbstractGame.cs" company="SingularityShift">
// Copyright (c) 2009 SingularityShift.com
// </copyright>
// <author>Ben McClure</author>
// <email>ben@singularityshift.com</email>
// <date>2009-08-14</date>
// <summary>Implements most of the IGame functionality for other classes to use</summary>
namespace GameLib.Games
{
    using System;
    using System.IO;
    using System.Runtime.Serialization.Formatters.Binary;
    using GameLib.Games.Components;
    using GameLib.Games.Util;

    /// <summary>
    /// An abstract implementation of the IGame interface. This should be overridden by individual game classes. For a generic game object, see the Game class.
    /// </summary>
    /// <typeparam name="T">The specific (or unspecific) IMod type this game handles</typeparam>
    public abstract class AbstractGame<T> : IGame<T>, IGame where T : IMod
    {
        #region Fields (9)

        /// <summary>
        /// An internal list of active mods.
        /// </summary>
        private IModCollection<T> activeMods;
        /// <summary>
        /// An internal string for the BinDir
        /// </summary>
        private string binDir;
        /// <summary>
        /// An internal string for the ConfigFile
        /// </summary>
        private string configFile;
        /// <summary>
        /// An internal string for the DataDir
        /// </summary>
        private string dataDir;
        /// <summary>
        /// An internal string for the DisabledModDir
        /// </summary>
        private string disabledModDir;
        /// <summary>
        /// An internal string for the Exe
        /// </summary>
        private string exe;
        /// <summary>
        /// An internal string representing the GameDir
        /// </summary>
        private string gameDir;
        /// <summary>
        /// An internal list of installed mods
        /// </summary>
        private IModCollection<T> installedMods;
        /// <summary>
        /// An internal string for the ModDir
        /// </summary>
        private string modDir;

        #endregion Fields

        #region Properties (11)

        /// <summary>
        /// <para>Gets the IModList of active mods for the implementing game.</para>
        /// <para>It is recommended to clear and import, rather than replacing the mod list through this setter, when this information changes. This avoids forcing methods which listen for events within the ActiveMods list to have to re-specify their listeners whenever the list changes.</para>
        /// </summary>
        public virtual IModCollection<T> ActiveMods
        {
            get
            {
                if (this.activeMods == null)
                {
                    this.activeMods = new ModCollection<T>(this);
                }

                return this.activeMods;
            }
        }

        /// <summary>
        /// Gets or sets the path to the directory of the game's executable.
        /// </summary>
        public virtual string BinDir
        {
            get
            {
                return this.binDir;
            }

            set
            {
                string oldValue = this.binDir;
                this.binDir = value;
                this.OnGameChanged(new GameChangedEventArgs(GameChangeType.BinDirChanged, oldValue));
            }
        }

        /// <summary>
        /// Gets or sets the path to the game's config file, if there is one. The type should be defined by the implementing class, unless it is well known by its users.
        /// </summary>
        public virtual string ConfigFile
        {
            get
            {
                return this.configFile;
            }

            set
            {
                string oldValue = this.configFile;
                this.configFile = value;
                this.OnGameChanged(new GameChangedEventArgs(GameChangeType.ConfigFileChanged, oldValue));
            }
        }

        /// <summary>
        /// Gets or sets the path to the top directory containing the game's data files.
        /// </summary>
        public virtual string DataDir
        {
            get
            {
                return this.dataDir;
            }

            set
            {
                string oldValue = this.dataDir;
                this.dataDir = value;
                this.OnGameChanged(new GameChangedEventArgs(GameChangeType.DataDirChanged, oldValue));
            }
        }

        /// <summary>
        /// Gets or sets the path to the top directory containing the game's disabled mods, if it differentiates these.
        /// </summary>
        public virtual string DisabledModDir
        {
            get
            {
                return this.disabledModDir;
            }

            set
            {
                string oldValue = this.disabledModDir;
                this.disabledModDir = value;
                this.OnGameChanged(new GameChangedEventArgs(GameChangeType.DisabledModDirChanged, oldValue));
            }
        }

        /// <summary>
        /// Gets or sets the path to the game's executable.
        /// </summary>
        public virtual string Exe
        {
            get
            {
                return this.exe;
            }

            set
            {
                string oldValue = this.exe;
                this.exe = value;
                this.OnGameChanged(new GameChangedEventArgs(GameChangeType.ExeChanged, oldValue));
            }
        }

        /// <summary>
        /// Gets or sets the full path to the game's installation directory
        /// </summary>
        public virtual string GameDir
        {
            get
            {
                return this.gameDir;
            }

            set
            {
                string oldValue = this.gameDir;
                this.gameDir = value;
                this.OnGameChanged(new GameChangedEventArgs(GameChangeType.GameDirChanged, oldValue));
            }
        }

        /// <summary>
        /// Gets or sets the game's id string (typically but not strictly its file name)
        /// </summary>
        public virtual string Id
        {
            get;
            set;
        }

        /// <summary>
        /// <para>Gets the IModList of active mods for the implementing game.</para>
        /// <para>It is recommended to clear and import, rather than replacing the mod list through this setter, when this information changes. This avoids forcing methods which listen for events within the ActiveMods list to have to re-specify their listeners whenever the list changes.</para>
        /// </summary>
        public virtual IModCollection<T> InstalledMods
        {
            get
            {
                if (this.installedMods == null)
                {
                    this.installedMods = new ModCollection<T>(this);
                }

                return this.installedMods;
            }
        }

        /// <summary>
        /// Gets or sets the path to the top directory containing the game's mods (if any).
        /// </summary>
        public virtual string ModDir
        {
            get
            {
                return this.modDir;
            }

            set
            {
                string oldValue = this.modDir;
                this.modDir = value;
                this.OnGameChanged(new GameChangedEventArgs(GameChangeType.ModDirChanged, oldValue));
            }
        }

        /// <summary>
        /// Gets or sets the game's full title
        /// </summary>
        public virtual string Title
        {
            get;
            set;
        }

        #endregion Properties

        #region Delegates and Events (1)

        // Events (1) 

        /// <summary>
        /// Thrown whenever anything about a game changes
        /// </summary>
        public event EventHandler<GameChangedEventArgs> GameChanged;

        #endregion Delegates and Events

        #region Methods (19)

        // Public Methods (18) 

        /// <summary>
        /// Creates a deep copy of the game overriding game object. All references to mods and other objects contained within the game will be replaced with new copies that are considered different but Equal.
        /// </summary>
        /// <returns>A new IGame instance of the same type as the object being cloned. The new object is equal but not the same.</returns>
        public virtual object Clone()
        {
            BinaryFormatter bf = new BinaryFormatter();
            MemoryStream ms = new MemoryStream();
            bf.Serialize(ms, this);
            ms.Flush();
            ms.Position = 0;
            return (IGame<T>)bf.Deserialize(ms);
        }

        /// <summary>
        /// Creates a new (blank) instance of the implementing mod's type. All the implementing mod should do is include a call to the desired constructor, with the desired default values.
        /// </summary>
        /// <returns>A blank instance of the implementing mod's type, which is invariably an IMod</returns>
        public abstract T CreateMod();

        /// <summary>
        /// When implemented (or supported by the game), this method disables or deactivates an installed mod in the game. This method should not physically uninstall the mod; if uninstallation is required, it should be done after disabling.
        /// </summary>
        /// <param name="mod">The IMod object describing the mod to deactivate</param>
        public virtual void DisableMod(IMod mod)
        {
            this.DisableMod((T)mod);
        }

        /// <summary>
        /// When overridden by an extending class, this method is designed to disable (or deactivate) a mod that exists on the system. In games that support it, this should have the effect of removing the mod from working in the game, without uninstalling it.
        /// </summary>
        /// <param name="mod">The IMod object describing the mod to disable</param>
        public abstract void DisableMod(T mod);

        /// <summary>
        /// When implemented (or supported by the game), this method enables or activates an installed mod in the game. This method should not physically install the mod; that should already be done.
        /// </summary>
        /// <param name="mod">The IMod object describing the mod to activate</param>
        public virtual void EnableMod(IMod mod)
        {
            this.EnableMod((T)mod);
        }

        /// <summary>
        /// When overridden by an extending class, this method is designed to enable (or activate) a mod that exists on the system. In games that support it, this should have the effect of making an installed (but deactivated) mod active in the game.
        /// </summary>
        /// <param name="mod">The IMod object describing the mod to enable</param>
        public abstract void EnableMod(T mod);

        /// <summary>
        /// When overridden by an extending class, this method is designed to export the activeMods list to the game, essentially putting it into effect.
        /// </summary>
        public abstract void ExportActiveMods();

        /// <summary>
        /// When overridden by an extending class, this method is designed to export the installedMods list to the game. This should not install anything, but should organize the installed mods according to the settings and order of the IModList.
        /// </summary>
        public abstract void ExportInstalledMods();

        /// <summary>
        /// Gets the ActiveMods of this game.
        /// </summary>
        /// <typeparam name="U">This must be the valid IMod type this game is using to work properly.</typeparam>
        /// <returns>The ActiveMods property of the game.</returns>
        public IModCollection<U> GetActiveMods<U>() where U : IMod
        {
            return (IModCollection<U>)ActiveMods;
        }

        /// <summary>
        /// Gets the InstalledMods of this game.
        /// </summary>
        /// <typeparam name="U">This must be the valid IMod type this game is using to work properly.</typeparam>
        /// <returns>The InstalledMods property of the game.</returns>
        public IModCollection<U> GetInstalledMods<U>() where U : IMod
        {
            return (IModCollection<U>)InstalledMods;
        }

        /// <summary>
        /// When overridden by an extending class, this method is designed to import the currently active mods into the activeMods property.
        /// </summary>
        public abstract void ImportActiveMods();

        /// <summary>
        /// When overridden by an extending class, this method is designed to import the currently installed mods into the activeMods property
        /// </summary>
        public abstract void ImportInstalledMods();

        /// <summary>
        /// Determines whether the provided IMod is active within the game
        /// </summary>
        /// <param name="theMod">The IMod to check for</param>
        /// <returns>A bool indicating whether the mod is active</returns>
        public virtual bool IsActive(IMod theMod)
        {
            return this.IsActive((T)theMod);
        }

        /// <summary>
        /// Checks if the provided IMod is currently active in the game.
        /// </summary>
        /// <param name="mod">The IMod object describing the mod to check for. This does not have to be the exact same object, but the content must be equal (as defined by the implementing class).</param>
        /// <returns>A bool signifying whether or not the mod is active.</returns>
        public virtual bool IsActive(T mod)
        {
            return this.ActiveMods.ContainsMod(mod);
        }

        /// <summary>
        /// Checks if the implementing game is currently installed.
        /// </summary>
        /// <returns>A bool signifying whether or not the game is installed.</returns>
        public abstract bool IsInstalled();

        /// <summary>
        /// Checks if the provided IMod is currently installed for the game.
        /// </summary>
        /// <param name="mod">The IMod object describing the mod to check for. This does not have to be the exact same object, but the content must be equal (as defined by the implementing class).</param>
        /// <returns>A bool signifying whether or not the mod is installed.</returns>
        public virtual bool IsInstalled(T mod)
        {
            return this.InstalledMods.Contains(mod);
        }

        /// <summary>
        /// Sets the activeMods property for this game to the given mod collection
        /// </summary>
        /// <param name="mods">The collection of mods to set as the currently-active mods</param>
        public virtual void SetActiveMods(IModCollection<T> mods)
        {
            this.ActiveMods.Clear();
            this.ActiveMods.FromArray(mods.ToArray());
            this.OnGameChanged(new GameChangedEventArgs(GameChangeType.ActiveModsReset));
        }

        /// <summary>
        /// Sets the installedMods property for this game to the given IModList
        /// </summary>
        /// <param name="mods">The collection of mods to set as the currently-installed mods</param>
        public virtual void SetInstalledMods(IModCollection<T> mods)
        {
            this.InstalledMods.Clear();
            this.InstalledMods.FromArray(mods.ToArray());
            this.OnGameChanged(new GameChangedEventArgs(GameChangeType.InstalledModsReset));
        }
        // Protected Methods (1) 

        /// <summary>
        /// This shortcut function should be called instead of the InstalledModsChanged event directly, to check that a handler is actually defined before throwing the event.
        /// </summary>
        /// <param name="eventArgs">This parameter should contain the GameChangedEventArgs to include with the event (Even if there are none)</param>
        protected void OnGameChanged(GameChangedEventArgs eventArgs)
        {
            if (this.GameChanged != null)
            {
                this.GameChanged(this, eventArgs);
            }
        }

        #endregion Methods
    }
}